﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Text.RegularExpressions;
using xxpl.DNS;
using xxpl.HTTP;
using xxpl.Utils;

namespace xxpl.GAE
{
    public class SimpleRequestHandler2 : IHttpRequestHandler
    {
        private const int RangeBlock = 500 * 1024;

        public void Execute(HttpListenerRequest request, HttpListenerResponse response)
        {
            RequestContext requestContext = RequestContext.Create(request);

            HttpWebResponse responseFromProxy = null;
            HttpWebRequest requestToProxy = null;
            try
            {
                while (true)
                {
                    requestToProxy = GetRequestToProxy(requestContext);

                    responseFromProxy = (HttpWebResponse)requestToProxy.GetResponse();

                    ProcessHeader(responseFromProxy, response);

                    ProcessResponse(responseFromProxy, response);

                    Console.WriteLine("{0}-{1} {2} {3}ms", requestToProxy.Host, requestContext.RawUrl, response.StatusCode, (int)(DateTime.Now - requestContext.RequestStartDate).TotalMilliseconds);

                    break;
                }
            }
            catch (Exception hex)
            {
                WebException wex = hex as WebException;
                if (wex != null && wex.Response != null)
                {
                    wex.Response.Close();
                }
                SendErrorPage(500, response);
            }
            finally
            {
                if (responseFromProxy != null)
                {
                    responseFromProxy.Close();
                }
                else
                {
                    if (requestToProxy != null)
                        requestToProxy.Abort();
                }
            }
        }

        public static int GetHeaderSize(HttpWebResponse httpWebResponse)
        {
            string[] len = httpWebResponse.GetResponseHeader("X-Length").Split(',');
            return int.Parse(len[0]);
        }

        public static int GetContentSize(HttpWebResponse httpWebResponse)
        {
            string[] len = httpWebResponse.GetResponseHeader("X-Length").Split(',');
            return int.Parse(len[1]);
        }

        public static byte GetKey(HttpWebResponse httpWebResponse)
        {
            int key = int.Parse(httpWebResponse.GetResponseHeader("X-Key"));
            return (byte)(key < 0 ? key + 256 : key);
        }

        public static byte[] ReadBytesFromStream(Stream stream, int len)
        {
            byte[] temp = new byte[len];
            int count = 0;
            do
            {
                count += stream.Read(temp, count, len - count);
            } while (count < len);

            return temp;
        }

        private static void SendErrorPage(int status, HttpListenerResponse response)
        {
            response.StatusCode = status;
            response.Abort();
        }

        private static void ProcessHeader(HttpWebResponse responseFromProxy, HttpListenerResponse response)
        {
            int headerSize = GetHeaderSize(responseFromProxy);

            byte[] headerBytes = ReadBytesFromStream(responseFromProxy.GetResponseStream(), headerSize);

            var ep = new XorEncryptor { Key = GetKey(responseFromProxy) };
            ep.Encrypt(headerBytes);

            string[] headers = Encoding.UTF8.GetString(headerBytes, 0, headerSize).Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            response.StatusCode = int.Parse(headers[0]);
            response.ContentLength64 = GetContentSize(responseFromProxy);

            for (int i = 1; i < headers.Length; i++)
            {
                int pos = headers[i].IndexOf(':');
                if (pos < 0) continue;

                string header = headers[i].Substring(0, pos).Trim();
                string value = headers[i].Substring(pos + 1).Trim();

                if (header.Equals("set-cookie", StringComparison.OrdinalIgnoreCase))
                {
                    ProcessCooike(response, value);
                }
                else if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.Parse(value) != GetContentSize(responseFromProxy))
                    {
                        Console.WriteLine("Wrong Data Size");
                    }
                    continue;
                }
                else response.AddHeader(header, value);
            }
        }

        private static void ProcessCooike(HttpListenerResponse response, string setCookies)
        {
            if (string.IsNullOrEmpty(setCookies)) return;

            string[] cookies = setCookies.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);

            List<string> cookieHeader = new List<string>();

            foreach (string cookie in cookies)
            {
                int x = cookie.IndexOf("=");
                int y = cookie.IndexOf(";");
                if (x < 0 || (y > -1 && x > y))
                {
                    cookieHeader[cookieHeader.Count - 1] = string.Format("{0}, {1}", cookieHeader[cookieHeader.Count - 1], cookie);
                }
                else
                {
                    cookieHeader.Add(cookie);
                }
            }

            foreach (string match in cookieHeader)
            {
                response.AddHeader("set-cookie", match);
            }
        }

        private static void ProcessResponse(HttpWebResponse responseFromProxy, HttpListenerResponse response)
        {
            int length = GetContentSize(responseFromProxy);

            int count = 0;
            var result = new byte[1024];

            using (Stream streamFromProxy = responseFromProxy.GetResponseStream())
            {
                var ep = new XorEncryptor { Key = GetKey(responseFromProxy) };

                while (count < length)
                {
                    int readed = streamFromProxy.Read(result, 0, length - count > result.Length ? result.Length : length - count);

                    if (readed == 0 || readed < 0)
                    {
                        break;
                    }

                    count += readed;
                    ep.Encrypt(result, 0, readed);
                    response.OutputStream.Write(result, 0, readed);
                }
                streamFromProxy.Close();
            }
            response.Close();
        }

        private static HttpWebRequest GetRequestToProxy(RequestContext request)
        {
            var requestToProxy = (HttpWebRequest)WebRequest.Create("https://" + DnsHelper.GetHost() + "/xpp2");
            requestToProxy.Host = DnsHelper.GetGaeHost(request.Host);
            requestToProxy.Method = "POST";
            requestToProxy.AllowAutoRedirect = false;
            requestToProxy.Proxy = null;
            requestToProxy.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);

            if (request.RangeStart.HasValue)
            {
                int to = request.RangeStart.Value + RangeBlock;
                if (to >= request.TotalRange)
                    request.HeaderCollection.Set(HttpRequestHeader.Range, string.Format("bytes={0}-", request.RangeStart.Value));
                else request.HeaderCollection.Set(HttpRequestHeader.Range, string.Format("bytes={0}-{1}", request.RangeStart.Value, to - 1));
            }

            using (Stream requestStream = requestToProxy.GetRequestStream())
            {
                var gZipStream = new DataZipStream(requestStream, CompressionMode.Compress);
                gZipStream.WriteBlock(request.HeaderCollection.ToString());
                gZipStream.WriteBlock(request.HttpMethod);
                gZipStream.WriteBlock(request.RawUrl);
                gZipStream.WriteBlock(request.PostData);
                gZipStream.Flush();
                gZipStream.Close();
                requestStream.Close();
            }
            return requestToProxy;
        }
    }
}